from OpenGL.GL import *
from OpenGL.GLU import *
import numpy
from OpenGL.GL.EXT.framebuffer_object import *
from OpenGL.GLUT import glutInit, glutInitDisplayMode, GLUT_DEPTH, GLUT_DOUBLE, GLUT_RGB, glutCreateWindow, glutSolidSphere
import PIL.Image
import sphere
import cloth
from helper import *

class Renderer:
    
    class RendererCamera:
        def __init__(self):
            self._matrix = numpy.array([[1, 0, 0, 0], \
                                       [0, 1, 0, 0], \
                                       [0, 0, 1, 0], \
                                       [0, 0, 0, 1]], dtype=numpy.float)
            
            self._azimuth = 0
            self._elevation = 0
            self._spin = 0
            self._translation = [0, 0, 0]
                     
        def Move(self, vec):
            self._translation[0] -= vec[0]
            self._translation[1] -= vec[1]
            self._translation[2] -= vec[2]
            self._UpdateMatrix()
            
        def _UpdateMatrix(self):
            glPushMatrix()
            glLoadIdentity()
            glRotate(self._azimuth, 0, 1, 0)
            glRotate(self._elevation, 1, 0, 0)
            glRotate(self._spin, 0, 0, 1)
            glTranslate(self._translation[0], self._translation[1], self._translation[2])
            self._matrix = glGetFloatv(GL_MODELVIEW_MATRIX)
            glPopMatrix()
        
        def Rotate(self, azimuth=0, elevation=0, spin=0):
            self._azimuth += azimuth
            self._elevation -= elevation
            self._spin += spin
            self._UpdateMatrix()
        
        def Matrix(self):
            return self._matrix
            
        
    def __init__(self, size):
        self._render_dictionary = dict()
        self._light = numpy.array([0.0, 13.0, 16.0, 1.0], dtype = numpy.float32)
        self.SetSize(size)
        self.Camera = Renderer.RendererCamera() 
        self._objects_to_render = []
        
        glutInit()
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
        glutCreateWindow("dump_window") 
         
        glClearColor(0.5, 0.2, 1.0, 1.0)
        glEnable(GL_DEPTH_TEST)
        glShadeModel(GL_SMOOTH)
        glEnable(GL_LIGHTING)
        glEnable(GL_LIGHT0)
        glEnable(GL_NORMALIZE)
        glEnable(GL_CULL_FACE)
        glCullFace(GL_BACK)
        glEnable(GL_LINE_SMOOTH)
        glEnable(GL_POLYGON_SMOOTH) 
         
        self._color_buffer = glGenRenderbuffersEXT(1)
        self._depth_buffer = glGenRenderbuffersEXT(1)
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, self._color_buffer)
        glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGB, self._w, self._h)
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, self._depth_buffer)
        glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, self._w, self._h)
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0)
        
        self._frame_burrer = glGenFramebuffersEXT(1)
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, self._frame_burrer)
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, self._depth_buffer)
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, self._color_buffer)
        
        status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)
        status_invalid = False
        if status != GL_FRAMEBUFFER_COMPLETE_EXT:
            status_invalid = True
        
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)
        
        if status_invalid:
            raise 'Frame buffer status invalid'
        
    def Register(self, class_, object_renderer):
        self._render_dictionary[class_] = object_renderer

    def SetSize(self, size):
        self._h = size[1]
        if self._h > GL_MAX_RENDERBUFFER_SIZE_EXT:
            self._h = GL_MAX_RENDERBUFFER_SIZE_EXT
            
        self._w = size[0]
        if self._w > GL_MAX_RENDERBUFFER_SIZE_EXT:
            self._w = GL_MAX_RENDERBUFFER_SIZE_EXT

    def _PreRender(self):
        
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, self._frame_burrer)
               
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glViewport(0, 0, self._w, self._h)
        
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(60.0, float(self._w) / float(self._h), 0.01, 100.0)
        
        glMatrixMode(GL_MODELVIEW)
        glLoadMatrixf(self.Camera.Matrix())       
        #light fixed -> does not move with the camera
        glLightfv(GL_LIGHT0, GL_POSITION, self._light)
    
    def _PostRender(self):
        self.output = glReadPixels(0, 0, self._w, self._h, GL_RGB, GL_UNSIGNED_BYTE), (self._w, self._h)
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)


    def _PerObjectPreRender(self):
        glPushAttrib(GL_ALL_ATTRIB_BITS)
        glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS)
        glPushMatrix()

    def _PerObjectPostRender(self):
        glPopMatrix();
        glPopClientAttrib();
        glPopAttrib();

    def AppendToRenderList(self, object_or_list):
        if getattr(object_or_list, "__iter__", None) != None:
            for object in object_or_list:
                self._objects_to_render.append(object)
        else:
            self._objects_to_render.append(object_or_list)

    def ClearRenderList(self):
        self._objects_to_render = []

    def Render(self):
        self._PreRender()
        for object in self._objects_to_render:
            renderer = self._render_dictionary[object.__class__]
            self._PerObjectPreRender()
            renderer(object)
            self._PerObjectPostRender()
        self._PostRender()

class SphereRenderer:
    def __call__(self, sphere):
        color = numpy.array([1.0, 0.0, 0.0, 1.0], dtype=numpy.float);
        glMaterialfv(GL_FRONT,GL_DIFFUSE, color)
        glTranslate(sphere.middle[0], sphere.middle[1], sphere.middle[2]);
        glutSolidSphere(sphere.radius - 0.03, sphere.slices, sphere.stacks);
        
class CubeRenderer:
    def __call__(self, cube):
        color = numpy.array([0.0, 1.0, 0.0, 1.0], dtype=numpy.float);
        shininess = 10;
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_NORMAL_ARRAY);
        
        glMaterialfv(GL_FRONT, GL_SHININESS, shininess);
        glMaterialfv(GL_FRONT, GL_DIFFUSE, color);
        glMaterialfv(GL_FRONT, GL_SPECULAR, color);
        
        i_0374 = [0, 4, 7, 3];
        self._RenderWall(i_0374, 0, cube);
    
        i_0154 = [0, 1, 5, 4];
        self._RenderWall(i_0154, 1, cube);
    
        i_1265 = [1, 2, 6, 5];
        self._RenderWall(i_1265, 2, cube);
    
        i_2673 = [2, 3, 7, 6];
        self._RenderWall(i_2673, 3, cube);
    
        i_0123 = [0, 3, 2, 1];
        self._RenderWall(i_0123, 4, cube);
    
        i_4567 = [4, 5, 6, 7];
        self._RenderWall(i_4567, 5, cube);
  
    def _RenderWall(self, indices, normalId, cube):
        glBegin(GL_QUADS);

        #print normals;
        for i in range(4): 
            glNormal3d(
                       cube.invWalls[normalId][i][0],
                       cube.invWalls[normalId][i][1],
                       cube.invWalls[normalId][i][2]);
            glVertex3d(
                       cube.vertices[indices[i]][0],
                       cube.vertices[indices[i]][1],
                       cube.vertices[indices[i]][2]);
    
        glEnd();
        
class PlaneRenderer:
    def __call__(self, plane):
#        print plane.vertices
#        print plane.indices
#        print plane.normals
        
        colorFront = numpy.array([0.0, 0.0, 1.0, 1.0], dtype=numpy.float);
        colorBack  = numpy.array([0.0, 1.0, 1.0, 1.0], dtype=numpy.float);
        shininess  = 10;
        
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_NORMAL_ARRAY);
        
        glMaterialfv(GL_FRONT, GL_SHININESS, shininess);
        glMaterialfv(GL_FRONT, GL_DIFFUSE,   colorFront);
        glMaterialfv(GL_FRONT, GL_SPECULAR,  colorFront);
        
        vertices = numpy.matrix(plane.vertices, copy=True)
        vertices[0] = 0.9 * (vertices[0] - plane.middle) + plane.middle
        vertices[1] = 0.9 * (vertices[1] - plane.middle) + plane.middle
        vertices[2] = 0.9 * (vertices[2] - plane.middle) + plane.middle
        vertices[3] = 0.9 * (vertices[3] - plane.middle) + plane.middle
        
        glVertexPointer(3, GL_FLOAT, 0, vertices);
        glNormalPointer(GL_FLOAT, 0, plane.normals);
        glDrawElements(GL_QUADS, 4, GL_UNSIGNED_INT, plane.indices);

        glMaterialfv(GL_FRONT, GL_SHININESS, shininess);
        glMaterialfv(GL_FRONT, GL_DIFFUSE,   colorBack);
        glMaterialfv(GL_FRONT, GL_SPECULAR,  colorBack);

        glCullFace(GL_FRONT);
        glNormalPointer(GL_FLOAT, 0, plane.inverseNormals);
        glDrawElements(GL_QUADS, 4, GL_UNSIGNED_INT, plane.indices);

class ClothRenderer:
    def __init__(self, cloth, normal_influence_radius = 3, normal_influence_variance = 1):
        self._indices_size = (cloth.points.shape[0] - 1) * (cloth.points.shape[1] - 1) * 6 
        self._indices = numpy.empty(self._indices_size, dtype=numpy.uint32)
        k = 0
        y = cloth.points.shape[0]
        x = cloth.points.shape[1]
        for i in range(y-1):
            for j in range(x-1):
                ii = x * i + j
                self._indices[k] = ii
                k += 1
                self._indices[k] = ii + 1
                k += 1
                self._indices[k] = ii + x
                k += 1
                self._indices[k] = ii + x
                k += 1
                self._indices[k] = ii + 1
                k += 1
                self._indices[k] = ii + x + 1
                k += 1
                
        self._normals = numpy.zeros_like(cloth.points)
        self._inverse_normals = numpy.empty_like(cloth.points)
        self._shininess = numpy.array([10], dtype=numpy.float)
        self._diffuseColor = numpy.array([1.0, 0.4, 0.8, 1.0], dtype=numpy.float) 
        self._specularColor = numpy.array([0.1, 0.1, 0.1, 1.0], dtype=numpy.float)
        
        self._normal_influence_radius = normal_influence_radius
        self._normal_influence_variance = normal_influence_variance
        self._normal_influence_window = influence_window(self._normal_influence_radius)
                
    def _CalculateNormals(self, points):
        y = points.shape[0]
        x = points.shape[1]
        
        counterpart = lambda (i,j): (j, -i)
        is_in_rect = lambda (i,j): i >= 0 and i < y and j >= 0 and j < x
        
        for i in range(y):
            for j in range(x):
                v = points[i, j]
                for p in self._normal_influence_window:
                    k = gauss_2d(p, self._normal_influence_variance)
                    cp = counterpart(p)
                    p = (p[0]+i, p[1]+j)
                    cp = (cp[0]+i, cp[1]+j)    
                    if is_in_rect(p) and is_in_rect(cp):
                        v1 = points[p]
                        v2 = points[cp]
                        vv1 = v1-v
                        vv2 = v2-v
                        vv = numpy.cross(vv1, vv2)
                        try:
                            self._normals[(i,j)] += k * vv / numpy.sqrt(numpy.dot(vv, vv))
                        except ZeroDivisionError:
                            print "ERROR: Cross product is 0 - two points must have collapsed"
                try:
                    self._normals[(i,j)] /= numpy.sqrt(numpy.dot(self._normals[(i,j)],self._normals[(i,j)]))
                except ZeroDivisionError:
                    print "ERROR: normal for (" + str(i) + "," + str(j) + ") is zero vector"
        
        self._inverse_normals = -self._normals
    
    def __call__(self, cloth):
        self._CalculateNormals(cloth.points)
        
        glMaterial(GL_FRONT, GL_SHININESS, self._shininess)
        glMaterial(GL_FRONT, GL_DIFFUSE, self._diffuseColor)
        glMaterial(GL_FRONT, GL_SPECULAR, self._specularColor)
        
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) #WIRE MODE
        glEnableClientState(GL_VERTEX_ARRAY)
        glEnableClientState(GL_NORMAL_ARRAY)
        
        glCullFace(GL_BACK)
        
        OpenGL.GL.glVertexPointer(3, GL_FLOAT, 0, cloth.points)
        OpenGL.GL.glNormalPointer(GL_FLOAT, 0, self._normals)
        OpenGL.GL.glDrawElements(GL_TRIANGLES, self._indices_size, GL_UNSIGNED_INT, self._indices)
    
        glCullFace(GL_FRONT)
        OpenGL.GL.glNormalPointer(GL_FLOAT, 0, self._inverse_normals)
        OpenGL.GL.glDrawElements(GL_TRIANGLES, self._indices_size, GL_UNSIGNED_INT, self._indices)
        

        
if __name__ == "__main__":
    renderer = Renderer((400, 400))
    
    
    import controller
    import constraint
    c = controller.SimulationController()
    s = cloth.Cloth(20, 20, 0.25)
    cc = constraint.ParticleSystemConstraints(20, 20, 3, 1, 0.25)
    c._cloth = s
    c._cloth_constraint = cc
    s2 = sphere.Sphere(0, -2.1, 0, 2)
    c._obstacle_list["s2"] = s2
    c._commands = []
    renderer.Register(sphere.Sphere, SphereRenderer())
    renderer.Register(cloth.Cloth, ClothRenderer(s))
    
    renderer.Camera.Move((-3, 3, 8))
    renderer.Camera.Rotate(elevation = -30, azimuth = 20)
    
    #render 1
    for i in range(20):
        print "render"
        renderer.AppendToRenderList(s)
        renderer.AppendToRenderList(s2)
        renderer.Render()
        renderer.ClearRenderList()
        print "save"
        img = PIL.Image.fromstring('RGB', renderer.output[1], renderer.output[0], 'raw', 'RGB', 0, 1)
        img = img.transpose(PIL.Image.FLIP_TOP_BOTTOM)
        img.save('img3'+str(i)+'.bmp', 'BMP')
        print "simulate"
        c._Simulate()
        print "iter end " + str(i)
    #render 2

#    renderer.AppendToRenderList(s)
#    renderer.AppendToRenderList(s2)
#    renderer.Render()
#    renderer.ClearRenderList()
#    img = PIL.Image.fromstring('RGB', renderer.output[1], renderer.output[0], 'raw', 'RGB', 0, 1)
#    img = img.transpose(PIL.Image.FLIP_TOP_BOTTOM)
#    img.save('img'+str(1)+'.bmp', 'BMP')
#        
#    for i in range(10):
#        renderer.AppendToRenderList(s)
#        renderer.Render()
#        renderer.ClearRenderList()
#        img = PIL.Image.fromstring('RGB', renderer.output[1], renderer.output[0], 'raw', 'RGB', 0, 1)
#        img = img.transpose(PIL.Image.FLIP_TOP_BOTTOM)
#        img.save('img'+str(i)+'.bmp', 'BMP')
#        s.StaticMove(0, 0, -1)